#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <signal.h>
#include <iostream>
#include <sstream>

#include "include/Jeu.h"
#include "include/Monde.h"
#include "include/Continent.h"
#include "include/Territoire.h"
#include "include/Joueur.h"
#include "include/PionManager.h"
#include "include/LoadModelVisitor.h"
#include "include/LoadBordersVisitor.h"
#include "include/LoadNameVisitor.h"
#include "include/RenderVisitor.h"
#include "include/MissionStrategy.h"
#include "include/Playlist.h"
#include "include/Partie.h"

#define DEBUG 0
#define BUFSIZE 512

using namespace std;


Jeu * Jeu::jeu = 0;

Jeu::Jeu()
{ 
   jeu = this;
   playlist = new Playlist();
   monde = new Monde();
   partie = new Partie();
   pionManager = new PionManager();
   loadModelVisitor = new LoadModelVisitor();
   loadBordersVisitor = new LoadBordersVisitor();
   renderVisitor = new RenderVisitor();
   loadNameVisitor = new LoadNameVisitor;
}

Jeu::~Jeu()
{
   //printf("Destruction de Jeu!\n");
   delete monde;
   delete pionManager;
   delete partie;
   delete loadModelVisitor;
   delete loadBordersVisitor;
   delete renderVisitor;
   delete loadNameVisitor;
   delete playlist;
}

void Jeu::toggleDisplayMode()
{
   renderVisitor->toggleDisplayMode();
}

Territoire * Jeu::select(int x, int y)
{
   glPushAttrib(GL_ALL_ATTRIB_BITS);    /* on sauvegarde tous */

   if ( glIsEnabled(GL_LIGHTING) )
      glDisable( GL_LIGHTING ); /* pas besoin de la lumiere pour le picking, desactiver tous ce qui est inutile */
   if ( glIsEnabled(GL_BLEND) )
      glDisable( GL_BLEND );
   if ( glIsEnabled(GL_FOG) )
      glDisable( GL_FOG );

   GLuint selectBuf[BUFSIZE];
   GLint viewport[4];
   
   glGetIntegerv (GL_VIEWPORT, viewport);
   
   glSelectBuffer (BUFSIZE, selectBuf);
   (void) glRenderMode (GL_SELECT);
   
   glInitNames();
   glPushName(0);
   
   glMatrixMode (GL_PROJECTION);
   glPushMatrix ();    /* push projection */
   glLoadIdentity ();
   gluPickMatrix ((GLdouble) x, (GLdouble) (viewport[3] - y), 2.0, 2.0, viewport);
   
   gluPerspective(scene.perspective.fovy,
                  scene.perspective.aspect,
                  scene.perspective.zNear,
                  scene.perspective.zFar);
   
   glMatrixMode(GL_MODELVIEW);
   glPushMatrix();        /* push modelview */
   glLoadIdentity();
   gluLookAt (scene.camera.eyeX, scene.camera.eyeY, scene.camera.eyeZ,
              scene.camera.centerX, scene.camera.centerY, scene.camera.centerZ,
              scene.camera.upX, scene.camera.upY, scene.camera.upZ);
   
   glTranslatef(-350.0f, -350.0f, 0.0f);
   monde->accept( loadNameVisitor );

   glPopMatrix();        /* pop modelview */
   glMatrixMode (GL_PROJECTION);   
   glPopMatrix ();        /* pop projection */

   if( glRenderMode(GL_RENDER) == 0 )
   {
      glPopAttrib();
      return NULL;    /* il n'y a pas de hits */
   }

   glPopAttrib();
   return monde->getIndexedTerritoire( selectBuf[(0<<2)+3] );
}

void Jeu::init()
{
   // Initialisation OpenGL
   glEnable (GL_DEPTH_TEST);
   glClearColor (1.0, 1.0, 1.0, 1.0);
   glClearIndex (0.0);
   glLoadIdentity ();
   
   gluLookAt (scene.camera.eyeX, scene.camera.eyeY, scene.camera.eyeZ,
              scene.camera.centerX, scene.camera.centerY, scene.camera.centerZ,
              scene.camera.upX, scene.camera.upY, scene.camera.upZ);
   
   // Init SDL audio
   if (SDL_Init(SDL_INIT_AUDIO) < 0) 
   {
      cerr << "Erreur d'initialisation de SDL : " << SDL_GetError() << endl;
      exit(EXIT_FAILURE);
   }
   
   // Initialisation des joueurs
   partie->initPlayers();
   
   // Initialisation des composants du jeu
   monde->accept( loadModelVisitor );
   monde->accept( loadBordersVisitor );
   
   // Repartition aleatoire des pions
   partie->distribuerTerritoires();

   // Musique d'intro
   playlist->playIntroduction();

   // Rendu de l'atmosphere
   float fogcolor[4] = { 0.5, 0.5, 0.5, 1.0 };
   glEnable(GL_FOG);
   glFogi(GL_FOG_MODE, GL_EXP2);
   glFogfv(GL_FOG_COLOR, fogcolor);
   glFogf(GL_FOG_DENSITY, 0.0007);

#ifdef FX
   glHint(GL_FOG_HINT, GL_NICEST);
#endif
}

void Jeu::_display()
{
   if(Jeu::jeu)
      Jeu::jeu->display(); 
}

 void Jeu::display()
 {
    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);

    if ( Jeu::jeu->scene.isFogEnabled() )
       glEnable(GL_FOG);
    else
       glDisable(GL_FOG);

    glLoadIdentity ();
    gluLookAt (Jeu::jeu->scene.camera.eyeX, Jeu::jeu->scene.camera.eyeY, Jeu::jeu->scene.camera.eyeZ,
               Jeu::jeu->scene.camera.centerX, Jeu::jeu->scene.camera.centerY, Jeu::jeu->scene.camera.centerZ,
               Jeu::jeu->scene.camera.upX, Jeu::jeu->scene.camera.upY, Jeu::jeu->scene.camera.upZ);    

    glPushMatrix();
    glTranslatef(-350.0, -350.0, 0);
    monde->accept( renderVisitor );
    glPopMatrix();

    if ( getScene()->getAnimationState(Animation::FEU) )
    {
       glPushMatrix();
       glTranslatef(-350.0, -350.0, 0);
       getScene()->drawFire();
       glPopMatrix();
    }

    glPushMatrix();
    glTranslatef(0.0, 150.0, 0);
    glScalef(1600.0, 800.0, 100.0);
    scene.drawSea();
    glPopMatrix();

    if( guinterface.isShowingHelpDialog() )
       guinterface.printHelpDialog();
    else
       guinterface.printHelpMessage();

    if( guinterface.isShowingNomJoueur() )
       guinterface.printNomJoueur();
    
    if ( getScene()->getAnimationState(Animation::DE) )
       getScene()->drawDe();
       
    guinterface.printStatusBar();
    
    if ( renderVisitor->getDisplayMode() == DisplayMode::POSSESSION )
       guinterface.printJoueursLegend( partie->getJoueurs() );

    if ( glIsEnabled(GL_FOG) )
       glDisable(GL_FOG);   

    glutSwapBuffers();
}

void Jeu::reshape(int width, int height)
{
   Jeu::jeu->guinterface.setWindowDimension(width, height);
   glViewport (0, 0, (GLsizei) width, (GLsizei) height); 
   glMatrixMode (GL_PROJECTION);
   glLoadIdentity ();
   Jeu::jeu->scene.perspective.setPerspective(70.0, width / (float) height , 0.1, 2000.0);
   gluPerspective(70.0, width / (float) height , 0.1, 2000.0);
   glMatrixMode (GL_MODELVIEW);
}

void Jeu::timer(int v)
{
   if ( Jeu::jeu->getScene()->isAnimated() )
      glutTimerFunc(Jeu::jeu->getScene()->getTimerAnimation(), timer, 1);
   glutPostRedisplay();
}

void Jeu::keyboard(unsigned char key, int x, int y)
{
   switch (key) 
   {
      case 32: // Space
         Jeu::jeu->scene.resetToDefault(); break;
      case 13: // Enter
         Jeu::jeu->getPartie()->nextStep(); break;
      case 'h':
         Jeu::jeu->guinterface.toggleShowingHelpDialog(); break;
      case 'd':
         Jeu::jeu->toggleDisplayMode(); break;
      case 'a':
         Jeu::jeu->scene.toggleFog(); break;
      case 'm':
         Jeu::jeu->getScene()->toggleAnimationState( Animation::SEA ); break;
      case 27: // Echap
         exit(0); break;
   }
   glutPostRedisplay();
}

void Jeu::mouse(int button, int state, int x, int y)
{
   int specialKey = glutGetModifiers();
   switch(button)
   {
      case GLUT_LEFT_BUTTON:
         // c'est mieu de le faire au clic, sinon on le fait 2Fois
         if( state == GLUT_DOWN )
         {
            Territoire * _territoire;
            _territoire = Jeu::jeu->select( x, y);
              
            if ( _territoire )
               Jeu::jeu->getPartie()->doStep( _territoire );
            else
               Jeu::jeu->playlist->playClic( Playlist::CLIC_SEA );
         }
         break;
      case 3: // WHEEL_UP
         if( specialKey == GLUT_ACTIVE_SHIFT )
            Jeu::jeu->scene.camera.centerY += 10.0;
         else 
            if( specialKey == GLUT_ACTIVE_CTRL )
               Jeu::jeu->scene.camera.upX += .01;
            else
               if( Jeu::jeu->scene.camera.eyeZ < -10 )
                  Jeu::jeu->scene.camera.eyeZ += 10.0;
         break;
      case 4: // WHEEL_DOWN
         if(specialKey == GLUT_ACTIVE_SHIFT)
            Jeu::jeu->scene.camera.centerY -= 10.0;
         else 
            if( specialKey == GLUT_ACTIVE_CTRL )
               Jeu::jeu->scene.camera.upX -= .01;
            else
               Jeu::jeu->scene.camera.eyeZ -= 10.0;
         break;
   }
   glutPostRedisplay();
}

void Jeu::mouseActiveMotion(int x, int y)
{
   Jeu::jeu->getScene()->moveCam(x, y);
   glutPostRedisplay();
}
 
void Jeu::mousePassiveMotion(int x, int y)
{
   Territoire * _territoire;
   Jeu::jeu->guinterface.messageStatus.clear();
   _territoire = Jeu::jeu->select(x, y);
   if ( _territoire && _territoire->getJoueur() )
   {
      std::ostringstream str;
      str << _territoire->countNbPions();
      Jeu::jeu->guinterface.messageStatus = string( _territoire->getContinent()->getNom() )
         + string( " : " )
         + string( _territoire->getNom() ) 
         + string( " / joueur:" )
         + string( _territoire->getJoueur()->getNom() )
         + string( " ( " )
         + str.str()
         + string( " pion(s) )" );
   }
   if ( _territoire 
        && (
           ( Jeu::jeu->getPartie()->getStep() == Step::CHOOSE_ARMY 
             && _territoire->appartientAuJoueur(Jeu::jeu->getPartie()->getCurrentPlayer()) )
           || ( 
              Jeu::jeu->getPartie()->getStep() == Step::CHOOSE_ENEMY 
              && ! _territoire->appartientAuJoueur(Jeu::jeu->getPartie()->getCurrentPlayer()) 
              && _territoire->isBorderOf(Jeu::jeu->getPartie()->getTerritoireArmy()) )
           )
      )
   {
      Jeu::jeu->getScene()->highlightTerritoire(_territoire); 
      glutPostRedisplay();
   }  
   else
      if( Jeu::jeu->getScene()->getTerritoireHighlighted() )
      {         
         Jeu::jeu->getScene()->highlightTerritoire(NULL);
         glutPostRedisplay();
      }
   Jeu::jeu->scene.mouse.x = x;
   Jeu::jeu->scene.mouse.y = y;
}

static void quit()
{
   SDL_Quit();
   delete Jeu::jeu;
   cout << "Fin: memoire libere" << endl;
}

static void gestion_sigint(int signum)
{
   quit();
}

int main(int argc, char ** argv)
{
   SDL_Thread * thread_anim = NULL;
   glutInit (&argc, argv);
   glutInitWindowSize (700, 700); 
   glutInitWindowPosition (100, 25);
   glutInitDisplayMode (GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE);
  
   if ( glutCreateWindow ("Risk - THE GAME") == GL_FALSE )
   {
      exit(1);
   }
  
   Jeu * jeu = new Jeu();
   jeu->init();
   jeu->guinterface.setWindowDimension(700, 700);
   glutDisplayFunc (Jeu::_display); 
   glutReshapeFunc (Jeu::reshape);
   glutKeyboardFunc (Jeu::keyboard);
   glutTimerFunc (40, Jeu::timer, 1);
   glutMouseFunc(Jeu::mouse);
   glutMotionFunc(Jeu::mouseActiveMotion);
   glutPassiveMotionFunc(Jeu::mousePassiveMotion);
   atexit(quit);
   if (signal (SIGINT, gestion_sigint) == SIG_ERR)
      printf("Probleme d'interception SIGINT\n");

   thread_anim = SDL_CreateThread(Scene::animIntroduction, NULL);
   if ( thread_anim == NULL ) 
   {
      printf("Unable to create thread: %s\n", SDL_GetError());
      exit(1);
   }

   glutMainLoop();   

   return 0;
}

